﻿// ValidationAspects
// Written by Mike Saunders
// Released under Microsoft Public License (Ms-PL)
using System.Linq;
using NUnit.Framework;
using ValidationAspects;
using ValidationAspects.Exceptions;
using ValidationAspects.Factories;
using ValidationAspects.PostSharp;

namespace TestValidationAspects.PostSharp
{
	[TestFixture]
	public class TestInterceptionValidation
	{
		#region Test Intercept Properties
		[Test]
		public void Test_Interception_for_all_properties()
		{
			TypeOf<ValidateAllProperties>.GetProperty(x => x.PropertyB).AddValidation(new[] {new NotNullOrEmpty()});
			typeof(ValidateAllProperties).GetProperty("StaticProperty").AddValidation(new[] {new NotNullOrEmpty()});

			ValidateAllProperties dummy = new ValidateAllProperties();

			Test.Throws<ValidateNotNullOrEmptyException>(() => dummy.AttributedProperty = null);
			Test.Throws<ValidateNotNullOrEmptyException>(() => dummy.PropertyB = null);
			Test.Throws<ValidateNotNullOrEmptyException>(() => ValidateAllProperties.StaticProperty = null);
		}

		[Test]
		public void Test_Interception_for_attributed_properties_only()
		{
			TypeOf<ValidateAttributedPropertiesOnly>.GetProperty(x => x.PropertyB).AddValidation(new[] {new NotNullOrEmpty()});

			ValidateAttributedPropertiesOnly dummy = new ValidateAttributedPropertiesOnly();

			Test.Throws<ValidateNotNullOrEmptyException>(() => dummy.AttributedProperty = null);
			dummy.PropertyB = null;
		}

		[Test]
		public void Test_Interception_on_virtual_properties()
		{
			Dummy dummy = new Dummy();
			Test.Throws<ValidateMaximumException>(() => dummy.Count = 6);
			Test.Throws<ValidateMinimumException>(() => dummy.Count = -1);
		}
		#endregion

		#region Test Intercept Methods
		[Test]
		public void Test_Interception_for_all_methods()
		{
			typeof(ValidateAllMethods).GetMethod("MethodB").GetParameters()[0].AddValidation(new[] {new NotNullOrEmpty()});
			typeof(ValidateAllMethods).GetMethod("StaticMethod").GetParameters()[0].AddValidation(new[] {new NotNullOrEmpty()});

			ValidateAllMethods dummy = new ValidateAllMethods();

			Test.Throws<ValidationContextException>(() => dummy.AttributedMethod(null));
			Test.Throws<ValidationContextException>(() => dummy.MethodB(null));
			Test.Throws<ValidationContextException>(() => ValidateAllMethods.StaticMethod(null));
		}

		[Test]
		public void Test_Interception_for_attributed_methods_only()
		{
			typeof(ValidateAttributedMethodsOnly).GetMethod("MethodB").GetParameters()[0].AddValidation(new[] {new NotNullOrEmpty()});

			ValidateAttributedMethodsOnly dummy = new ValidateAttributedMethodsOnly();

			Test.Throws<ValidationContextException>(() => dummy.AttributedMethod(null));
			dummy.MethodB(null);
		}
		#endregion

		#region Test Generic Types
		[Test]
		public void Test_generic_class_is_object_validatable()
		{
			GenericDummy<string> genericDummy = new GenericDummy<string>();

			genericDummy.Method("s"); // succeeds

			genericDummy.ReportObjectIsInvalid = true;

			try
			{
				genericDummy.Method("s");
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateObjectException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_class_is_parameter_validatable()
		{
			GenericDummy<string> genericDummy = new GenericDummy<string>();

			try
			{
				genericDummy.Method(null);
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateNotNullOrEmptyException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_class_is_generic_parameter_validatable()
		{
			GenericDummy<string> genericDummy = new GenericDummy<string>();

			try
			{
				genericDummy.GenericParameterMethod(null);
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateNotNullException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_class_is_non_generic_property_validatable()
		{
			GenericDummy<string> genericDummy = new GenericDummy<string> {StringProperty = "s"};
			Test.Throws<ValidateNotEqualException>(() => genericDummy.StringProperty = "none");
		}

		[Test]
		public void Test_generic_class_is_generic_property_validatable()
		{
			GenericDummy<string> genericDummy = new GenericDummy<string> {GenericProperty = "s"};
			Test.Throws<ValidateNotEqualException>(() => genericDummy.GenericProperty = "none");
		}

		[Test]
		public void Test_type_derived_from_base_is_validatable()
		{
			ClosedDomainModel model = new ClosedDomainModel();
			Test.Throws<ValidationContextException>(() => model.BaseMethod(null));
			Test.Throws<ValidateNotNullException>(() => model.BaseProperty = null);
			Test.Throws<ValidationContextException>(() => model.Method(null));
			Test.Throws<ValidateNotNullException>(() => model.Property = null);
		}

		[Test]
		public void Test_type_derived_from_base_decorated_with_ValidateAttribute_is_validatable()
		{
			ClosedAttributedDomainModel model = new ClosedAttributedDomainModel();
			Test.Throws<ValidationContextException>(() => model.BaseMethod(null));
			Test.Throws<ValidateNotNullException>(() => model.BaseProperty = null);
			Test.Throws<ValidationContextException>(() => model.Method(null));
			Test.Throws<ValidateNotNullException>(() => model.Property = null);
		}

		[Test]
		public void Test_closed_type_derived_from_generic_base_is_validatable()
		{
			ClosedGenericDomainModel model = new ClosedGenericDomainModel();
			Test.Throws<ValidationContextException>(() => model.BaseMethod(null));
			Test.Throws<ValidateNotNullException>(() => model.BaseProperty = null);
			Test.Throws<ValidationContextException>(() => model.Method(null));
			Test.Throws<ValidateNotNullException>(() => model.Property = null);
		}

		[Test]
		public void Test_closed_type_derived_from_generic_base_decorated_with_ValidateAttribute_is_validatable()
		{
			ClosedGenericAttributedDomainModel model = new ClosedGenericAttributedDomainModel();
			Test.Throws<ValidationContextException>(() => model.BaseMethod(null));
			Test.Throws<ValidateNotNullException>(() => model.BaseProperty = null);
			Test.Throws<ValidationContextException>(() => model.Method(null));
			Test.Throws<ValidateNotNullException>(() => model.Property = null);
		}
		#endregion

		#region Test Generic Methods
		[Test]
		public void Test_generic_method_of_non_generic_class_is_object_validated()
		{
			GenericMethodDummy genericMethodDummy = new GenericMethodDummy();

			genericMethodDummy.Method("T", "s"); // succeeds

			genericMethodDummy.ReportObjectIsInvalid = true;

			try
			{
				genericMethodDummy.Method("T", "s");
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateObjectException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_method_of_non_generic_class_is_non_generic_parameter_validated()
		{
			GenericMethodDummy genericMethodDummy = new GenericMethodDummy();
			try
			{
				genericMethodDummy.Method("T", null);
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateNotNullOrEmptyException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_method_of_non_generic_class_is_generic_parameter_validated()
		{
			GenericMethodDummy genericMethodDummy = new GenericMethodDummy();
			try
			{
				genericMethodDummy.Method<string>(null, "s");
			}
			catch (ValidationContextException e)
			{
				Assert.IsTrue(e.Flatten().First().GetType() == typeof(ValidateNotNullOrEmptyException));
				return;
			}

			Assert.Fail("ValidationContextException not thrown");
		}

		[Test]
		public void Test_generic_method_of_non_generic_class_throws_when_resolving_mismatching_validator_applied_to_generic_parameter()
		{
			GenericMethodDummy genericMethodDummy = new GenericMethodDummy();
			Test.Throws<ValidationException>(() => genericMethodDummy.Method(123, "s"), string.Format("Failed to construct validator for [type:{0}] [method:Method] [parameter:t].", typeof(GenericMethodDummy)));
		}
		#endregion

		#region Mocks
		[Validate(AttributedPropertiesOnly = true)]
		public class ValidateAttributedPropertiesOnly
		{
			[NotNullOrEmpty]
			public string AttributedProperty { get; set; }

			public string PropertyB { get; set; }
		}

		[Validate]
		public class ValidateAllProperties
		{
			[NotNullOrEmpty]
			public string AttributedProperty { get; set; }

			public string PropertyB { get; set; }

			public static string StaticProperty { get; set; }
		}

		[Validate(AttributedMethodsOnly = true)]
		public class ValidateAttributedMethodsOnly
		{
			public void AttributedMethod([NotNullOrEmpty] string s) {}

			public void MethodB(string s) {}
		}

		[Validate]
		public class ValidateAllMethods
		{
			public void AttributedMethod([NotNullOrEmpty] string s) {}

			public void MethodB(string s) {}

			public static void StaticMethod(string s) {}
		}

		[Validate]
		public class GenericDummy<T>
		{
			public bool ReportObjectIsInvalid { get; set; }

			[ValidateObject]
			public void Method([NotNullOrEmpty] string s) {}

			[ValidateObject]
			public void GenericParameterMethod([NotNull] T t) {}

			[NotEqual("none")]
			public string StringProperty { get; set; }

			[NotEqual("none")]
			public T GenericProperty { get; set; }

			[ValidationMethod]
			public static void ValidateDummy(GenericDummy<T> genericDummy)
			{
				if (genericDummy.ReportObjectIsInvalid)
					throw new ValidationException("invalid dummy");
			}
		}

		[Validate]
		public class GenericMethodDummy
		{
			public bool ReportObjectIsInvalid { get; set; }

			[ValidateObject]
			public void Method<T>([NotNullOrEmpty] T t, [NotNullOrEmpty] string s) {}

			[ValidationMethod]
			public static void ValidateDummy(GenericMethodDummy genericMethodDummy)
			{
				if (genericMethodDummy.ReportObjectIsInvalid)
					throw new ValidationException("invalid dummy");
			}
		}

		[Validate]
		public abstract class DummyBase
		{
			[Minimum(0)]
			public virtual int Count { get; set; }
		}

		[Validate]
		public class Dummy : DummyBase
		{
			[Maximum(5)]
			public override int Count
			{
				get
				{
					return base.Count;
				}
				set
				{
					base.Count = value;
				}
			}
		}

		public class DomainModelBase
		{
			public void BaseMethod([NotNull] string s) { }
			[NotNull]
			public string BaseProperty { get; set; }
		}

		[Validate]
		public class ClosedDomainModel : DomainModelBase
		{
			public void Method([NotNull] string s) { }
			[NotNull]
			public string Property { get; set; }
		}

		[Validate]
		public class AttributedDomainModelBase
		{
			public void BaseMethod([NotNull] string s) { }
			[NotNull]
			public string BaseProperty { get; set; }
		}

		[Validate]
		public class ClosedAttributedDomainModel : DomainModelBase
		{
			public void Method([NotNull] string s) { }
			[NotNull]
			public string Property { get; set; }
		}

		public class GenericDomainModelBase<T>
		{
			public void BaseMethod([NotNull] T s) { }
			[NotNull]
			public T BaseProperty { get; set; }
		}

		[Validate]
		public class ClosedGenericDomainModel : GenericDomainModelBase<string>
		{
			public void Method([NotNull] string s) { }
			[NotNull]
			public string Property { get; set; }
		}

		[Validate]
		public class AttributedGenericDomainModelBase<T>
		{
			public void BaseMethod([NotNull] T s) { }
			[NotNull]
			public T BaseProperty { get; set; }
		}

		[Validate]
		public class ClosedGenericAttributedDomainModel : GenericDomainModelBase<string>
		{
			public void Method([NotNull] string s) { }
			[NotNull]
			public string Property { get; set; }
		}
		#endregion
	}
}